/*
 * Reksio - Memory Map Editor
 * Copyright (C) 2023 CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In applying this licence, CERN does not waive the privileges and immunities
 * granted to it by virtue of its status as an Intergovernmental Organization or
 * submit itself to any jurisdiction.
 */

#include "python_mappings.h"
#include "pybind11/operators.h"

#include <QDebug>
#include <QStandardPaths>
#include <QPushButton>
#include <QMessageBox>
#include <QInputDialog>
#include <QFileDialog>

#include <QApplication>
#include <QProcess>

#include <QDesktopServices>

#include "mainwindow.h"
#include "attribute.h"
#include "memorynode.h"
#include "nodesmodel.h"
#include "attributesmodel.h"
#include "utils.h"
#include "commands.h"
#include "customnodesview.h"
#include "filewatcher.h"
#include "pythoninterpreterlineedit.h"


PYBIND11_EMBEDDED_MODULE(reksio, m) {
    // QString class
    py::class_<QString>(m, "QString")
            .def(py::init(&QString::fromStdString))
            .def("__str__", [](QString& str) { return str.toStdString();})
            ;
    py::implicitly_convertible<std::string, QString>();

    py::class_<PyStringIOLikeRedirecting>(m, "StringIOLikeRedir")
            .def(py::init())
            .def("close", &PyStringIOLikeRedirecting::noop)
            .def("fileno", &PyStringIOLikeRedirecting::noop)
            .def("flush", &PyStringIOLikeRedirecting::noop)
            .def("isatty", &PyStringIOLikeRedirecting::noop)
            .def("readable", &PyStringIOLikeRedirecting::noop)
            .def("readline", &PyStringIOLikeRedirecting::noop)
            .def("readlines", &PyStringIOLikeRedirecting::noop)
            .def("seek", &PyStringIOLikeRedirecting::noop)
            .def("seekable", &PyStringIOLikeRedirecting::noop)
            .def("tell", &PyStringIOLikeRedirecting::noop)
            .def("truncate", &PyStringIOLikeRedirecting::noop)
            .def("writable", &PyStringIOLikeRedirecting::noop)
            // only write does something
            .def("write", &PyStringIOLikeRedirecting::write)

            ;

    // main window getter hack - a bit nasty, but... is required for some validators
    m.def("get_main_window", &MainWindow::getInstance, py::return_value_policy::reference);
    m.def("popup_warning",  [](const QString& title, const QString& text){ return QMessageBox::warning(MainWindow::getInstance(), title, text);});
    m.def("popup_info",     [](const QString& title, const QString& text){ return QMessageBox::information(MainWindow::getInstance(), title, text);});
    m.def("popup_question", [](const QString& title, const QString& text){ return QMessageBox::question(MainWindow::getInstance(), title, text);});
    m.def("popup_critical", [](const QString& title, const QString& text){ return QMessageBox::critical(MainWindow::getInstance(), title, text);});


    m.def("fork", [](std::vector<std::string>& args){
        const QString app = QCoreApplication::applicationFilePath();
        QProcess* process = new QProcess(MainWindow::getInstance());
        QStringList arguments;
        for(auto arg : args)
        {
            arguments.push_back(QString::fromStdString(arg));
        }
        process->setArguments(arguments);
        process->setProgram(app);
        process->start();
    });

    m.def("fork_detached", [](std::vector<std::string>& args){
        const QString app = QCoreApplication::applicationFilePath();
        QProcess* process = new QProcess(MainWindow::getInstance());
        QStringList arguments;
        for(auto arg : args)
        {
            arguments.push_back(QString::fromStdString(arg));
        }
        process->setArguments(arguments);
        process->setProgram(app);
        process->startDetached();
    });

    m.def("openExplorer", [](const std::string& path){
        QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path)));
    });

    // Attribute class
    py::class_<Attribute>(m, "Attribute")
            .def(py::init<const std::string&>())
            .def_property_readonly("name", &Attribute::getName, py::return_value_policy::reference_internal)
            .def_property_readonly("fullname",
                [](const Attribute& a) {
                    return join(a.getFullName(), "/");
                })
            .def("__repr__",
                [](const Attribute& a) {
                     return "<reksio.Attribute '" + join(a.getFullName(), "/") + "' = '" + a.getValue() + "'>";
                })
            .def_property("value", &Attribute::getValue, &Attribute::setValue, py::return_value_policy::reference_internal)
            .def_property("parent", &Attribute::getParent, &Attribute::setParent, py::return_value_policy::reference)
            .def_property_readonly("parentNode", &Attribute::getParentNode, py::return_value_policy::reference)
            .def_property("savable", &Attribute::isSavable, &Attribute::setSavable)
            .def_property("editable", &Attribute::isEditable, &Attribute::setEditable)
                 ;
    // PythonAttribute
    // AttributeContainer class
    py::class_<AttributeContainer>(m, "AttributeContainer")
            .def_property_readonly("name", &AttributeContainer::getName, py::return_value_policy::reference_internal)
            .def_property_readonly("parent", &AttributeContainer::getParent, py::return_value_policy::reference)
            .def_property_readonly("parentNode", &AttributeContainer::getParentNode, py::return_value_policy::reference)
            .def("isRoot", &AttributeContainer::isRoot)
            .def("getRoot", py::overload_cast<>(&AttributeContainer::getRoot), py::return_value_policy::reference)
            .def("__getitem__",
                 [](const AttributeContainer& n, py::str key) {
                    const std::string& str_key = key.cast<std::string>();
                    Attribute * attr = n.getAttribute(str_key);
                    if (attr)
                        return attr;
                    throw py::key_error("key '" + str_key + "' does not exist");
                 }, py::return_value_policy::reference_internal)
            .def("__getattr__",
                 [](const AttributeContainer& n, py::str key) {
                    const std::string& str_key = key.cast<std::string>();
                    Attribute * attr = n.getAttribute(str_key);
                    if (attr)
                        return attr;
                    throw py::attribute_error("attribute '" + str_key + "' does not exist");
                 }, py::return_value_policy::reference_internal)
            .def("addAttribute", py::overload_cast<Attribute*>(&AttributeContainer::addAttribute))
            .def("addAttributeContainer", py::overload_cast<const std::string&>(&AttributeContainer::addContainer))
            .def("getAttributeContainer", py::overload_cast<const std::string&>(&AttributeContainer::getAttributeContainer, py::const_), py::return_value_policy::reference)
            .def("getAttributeContainer", py::overload_cast<const std::vector<std::string>&>(&AttributeContainer::getAttributeContainer, py::const_), py::return_value_policy::reference)
            .def("getAttribute", py::overload_cast<const std::string&>(&AttributeContainer::getAttribute, py::const_), py::return_value_policy::reference)
            .def("getAttribute", py::overload_cast<const std::vector<std::string>&>(&AttributeContainer::getAttribute, py::const_), py::return_value_policy::reference)
            ;
    // MemoryNode class
    py::class_<MemoryNode>(m, "MemoryNode")
            .def(py::init<const std::string&>())
            .def_property("type", &MemoryNode::getType, &MemoryNode::setType, py::return_value_policy::reference_internal)
            .def_property("parent", &MemoryNode::getParent, &MemoryNode::setParent, py::return_value_policy::reference)
            .def("index", &MemoryNode::index)
            .def("children",
                 [](const MemoryNode& n)
                {
                    std::vector<MemoryNode*> nodes;
                    std::transform(n.getChildren().begin(), n.getChildren().end(), std::back_inserter(nodes), [](const std::unique_ptr<MemoryNode>& a){return a.get();});
                    return nodes;
                }, py::return_value_policy::reference_internal)
            .def("attribute_container",
                 [](const MemoryNode& n)
                {
                    return n.getAttributeContainer();
                }, py::return_value_policy::reference_internal)
            .def("attributes",
                 [](const MemoryNode& n)
                {
                    return n.getAttributeContainer()->getAllAttributes();
                }, py::return_value_policy::reference_internal)
            .def_property_readonly("name", &MemoryNode::getName)
            .def_property_readonly("root", &MemoryNode::getRoot, py::return_value_policy::reference)
            .def("__repr__",
                 [](const MemoryNode& n) {
                     return "<reksio.MemoryNode type = '" + n.getType() + "', name = '" + join(n.nodeLocation(), "/") + "'>";
                 })
            .def("__getitem__",
                 [](const MemoryNode& n, py::str key) {
                    const std::string& str_key = key.cast<std::string>();
                    Attribute * attr = n.getAttributeContainer()->getAttribute(str_key);
                    if (attr)
                        return attr;
                    throw py::key_error("key '" + str_key + "' does not exist");
                 }, py::return_value_policy::reference_internal)
            .def("__getattr__",
                 [](const MemoryNode& n, py::str key) {
                    const std::string& str_key = key.cast<std::string>();
                    Attribute * attr = n.getAttributeContainer()->getAttribute(str_key);
                    if (attr)
                        return attr;
                    throw py::attribute_error("attribute '" + str_key + "' does not exist");
                 }, py::return_value_policy::reference_internal)
            .def("validate", &MemoryNode::validate)
            .def("isValid", &MemoryNode::isValid)
            .def_property("editable", &MemoryNode::isEditable, &MemoryNode::setEditable)
            // MODIFIERS
            .def("addChild", py::overload_cast<MemoryNode*>(&MemoryNode::addChild))
            .def("removeChild", py::overload_cast<MemoryNode*>(&MemoryNode::removeChild))
            .def("removeAllChildren", &MemoryNode::removeChildren)
            .def_property("savable", &MemoryNode::isSavable, &MemoryNode::setSavable)
            .def("getRoot", &MemoryNode::getRoot, py::return_value_policy::reference)
            .def("getChildByType", py::overload_cast<const std::string&>(&MemoryNode::getChild, py::const_), py::return_value_policy::reference)
            .def("getChildByType", py::overload_cast<const std::vector<std::string>&>(&MemoryNode::getChild, py::const_), py::return_value_policy::reference)
            .def("getAttribute",
                 [](const MemoryNode& n, const std::string& key) {
                    return n.getAttributeContainer()->getAttribute(key);
                 }, py::return_value_policy::reference_internal)
            .def("getAttribute",
                 [](const MemoryNode& n, const std::vector<std::string>& keys) {
                    return n.getAttributeContainer()->getAttribute(keys);
                 }, py::return_value_policy::reference_internal)
            .def("nodeLocation", &MemoryNode::nodeLocation)
            ;
    // MainWindow class
    py::class_<MainWindow>(m, "MainWindow")
            .def("getSaveConfirmation", &MainWindow::getSaveConfirmation)
            .def("getSaveSubmapConfirmation", &MainWindow::getSaveSubmapConfirmation)
            .def("callPythonOnLoadScript", &MainWindow::callPythonOnLoadScript)
            .def("dataChanged", py::overload_cast<MemoryNode*>(&MainWindow::itemChanged))
            .def("dataChanged", py::overload_cast<Attribute*>(&MainWindow::itemChanged))
            .def("getNodesModel", &MainWindow::getNodesModel, py::return_value_policy::reference)
            .def("getAttributesModel", &MainWindow::getAttributesModel, py::return_value_policy::reference)
            .def("getCurrentAttributesModel", &MainWindow::getCurrentAttributesModel, py::return_value_policy::reference)
            .def("hasFile", &MainWindow::hasFile)
            .def("getPromptSubmap", &MainWindow::getPromptSubmap)
            .def("getPromptMainmap", &MainWindow::getPromptMainmap)
            .def("setPromptSubmap", &MainWindow::setPromptSubmap)
            .def("setPromptMainmap", &MainWindow::setPromptMainmap)
            .def_property_readonly("currentFile", &MainWindow::getOpenedFilename)
            .def("nodesViewCurrentChanged", &MainWindow::nodesViewCurrentChanged)
            .def("isModified", &MainWindow::isModified)
            .def("reload", &MainWindow::reload)
            .def("getNodesView", &MainWindow::getNodesView, py::return_value_policy::reference)
            .def("getUndoStack", &MainWindow::getUndoStack, py::return_value_policy::reference)
            ;
    // NodesModel class
    py::class_<NodesModel>(m, "NodesModel")
            .def("getRoot", &NodesModel::getRoot, py::return_value_policy::reference)
            .def("removeChild", &NodesModel::removeChild)
            .def("parent", &NodesModel::parent)
            .def("index", &NodesModel::index)
            .def("rowCount", &NodesModel::rowCount)
            .def("getIndex", &NodesModel::getIndex)
            .def("insertChild", py::overload_cast<int, MemoryNode *, const QModelIndex &>(&NodesModel::insertChild))
            .def("isModified", &NodesModel::isModified, py::arg("index"), py::arg("recursive") = true)
            .def("save", &NodesModel::save)
            .def("getItem", &NodesModel::getItem, py::return_value_policy::reference)
            .def("getAttributesModel", &NodesModel::getAttributesModel, py::return_value_policy::reference)
            .def("isParent", &NodesModel::isParent)
            .def("getModifiedIndexes", &NodesModel::getModifiedIndexes)
            .def("clearAttributesModels", &NodesModel::clearNotUsedAttributesModels)
            ;
    // CustomNodesView
    py::class_<CustomNodesView>(m, "NodesView")
            .def("selectedIndexes", [](CustomNodesView* nv){
        auto res = getOnlyColumn0(nv->selectionModel()->selectedIndexes());
        std::list<QModelIndex> lst(res.begin(), res.end());
        return lst;})
            .def("collapseAll", &CustomNodesView::collapseAll)
    ;

    // AttributesModel class
    py::class_<AttributesModel>(m, "AttributesModel")
            .def("rowCount", &AttributesModel::rowCount)
            .def("getAttribute", &AttributesModel::getAttribute, py::return_value_policy::reference)
            .def("getContainer", &AttributesModel::getContainer, py::return_value_policy::reference)
            .def("getParentContainer", &AttributesModel::getParentContainer, py::return_value_policy::reference)
            .def("isAttribute", &AttributesModel::isAttribute)
            .def("isContainer", &AttributesModel::isContainer)
            .def("getParentMemoryNode", &AttributesModel::getParentMemoryNode, py::return_value_policy::reference)
            .def("getNodesModel", &AttributesModel::getNodesModel, py::return_value_policy::reference)
            .def("getIndex", py::overload_cast<Attribute*>(&AttributesModel::getIndex, py::const_), py::return_value_policy::reference)
            .def("getIndex", py::overload_cast<AttributeContainer*>(&AttributesModel::getIndex, py::const_), py::return_value_policy::reference)
            ;
    // QUndoStack class
    py::class_<QUndoStack>(m, "QUndoStack")
            .def("beginMacro", &QUndoStack::beginMacro)
            .def("endMacro", &QUndoStack::endMacro)
            .def("clear", &QUndoStack::clear)
            .def("resetClean", &QUndoStack::resetClean)
            ;

    // QModelIndex class
    py::class_<QModelIndex>(m, "QModelIndex")
            .def("row", &QModelIndex::row)
            .def("column", &QModelIndex::column)
            .def("parent", &QModelIndex::parent)
            .def("sibling", &QModelIndex::sibling)
            .def("siblingAtColumn", &QModelIndex::siblingAtColumn)
            .def("siblingAtRow", &QModelIndex::siblingAtRow)
            .def("isValid", &QModelIndex::isValid)
            .def("__repr__", [](const QModelIndex& idx){return std::string("<reksio.QModelIndex row: ") + QString::number(idx.row()).toStdString() + " column: " + QString::number(idx.column()).toStdString() + ">";})
            .def(py::self == QModelIndex())
            .def(py::self != QModelIndex())
            .def("getAttributesModel", [](const QModelIndex& idx){ return const_cast<AttributesModel*>(static_cast<const AttributesModel*>(idx.model())); }, py::return_value_policy::reference)
            .def("getNodesModel", [](const QModelIndex& idx){ return const_cast<NodesModel*>(static_cast<const NodesModel*>(idx.model())); }, py::return_value_policy::reference)
            ;

    // QMessageBox class
    py::class_<QMessageBox> message_box(m, "QMessageBox");
            message_box.def(py::init<MainWindow*>(), py::arg("parent"), py::return_value_policy::reference);
            message_box.def(py::init<>());
            message_box.def("addButton", py::overload_cast<const QString &, QMessageBox::ButtonRole>(&QMessageBox::addButton), py::return_value_policy::reference_internal);
            message_box.def("addButton", py::overload_cast<QMessageBox::StandardButton>(&QMessageBox::addButton), py::return_value_policy::reference_internal);
            message_box.def("exec", &QMessageBox::exec);
            message_box.def("clickedButton", &QMessageBox::clickedButton, py::return_value_policy::reference_internal);
            message_box.def("setText", &QMessageBox::setText);
            message_box.def("setIcon", &QMessageBox::setIcon);
            message_box.def("setDetailedText", &QMessageBox::setDetailedText);
            message_box.def("setDefaultButton", py::overload_cast<QMessageBox::StandardButton>(&QMessageBox::setDefaultButton));
            message_box.def("setDefaultButton", py::overload_cast<QPushButton*>(&QMessageBox::setDefaultButton));
            ;
    // QMessageBox::StandardButton enum
    py::enum_<QMessageBox::StandardButton>(message_box, "StandardButton")
            .value("NoButton", QMessageBox::StandardButton::NoButton)
            .value("Ok", QMessageBox::StandardButton::Ok)
            .value("Save", QMessageBox::StandardButton::Save)
            .value("SaveAll", QMessageBox::StandardButton::SaveAll)
            .value("Open", QMessageBox::StandardButton::Open)
            .value("Yes", QMessageBox::StandardButton::Yes)
            .value("YesToAll", QMessageBox::StandardButton::YesToAll)
            .value("No", QMessageBox::StandardButton::No)
            .value("NoToAll", QMessageBox::StandardButton::NoToAll)
            .value("Abort", QMessageBox::StandardButton::Abort)
            .value("Retry", QMessageBox::StandardButton::Retry)
            .value("Ignore", QMessageBox::StandardButton::Ignore)
            .value("Close", QMessageBox::StandardButton::Close)
            .value("Cancel", QMessageBox::StandardButton::Cancel)
            .value("Discard", QMessageBox::StandardButton::Discard)
            .value("Help", QMessageBox::StandardButton::Help)
            .value("Apply", QMessageBox::StandardButton::Apply)
            .value("Reset", QMessageBox::StandardButton::Reset)
            .value("RestoreDefaults", QMessageBox::StandardButton::RestoreDefaults)
            ;

    py::enum_<QMessageBox::ButtonRole>(message_box, "ButtonRole")
            .value("ActionRole", QMessageBox::ButtonRole::ActionRole)
            ;

    py::enum_<QMessageBox::Icon>(message_box, "Icon")
            .value("NoIcon", QMessageBox::Icon::NoIcon)
            .value("Information", QMessageBox::Icon::Information)
            .value("Warning", QMessageBox::Icon::Warning)
            .value("Critical", QMessageBox::Icon::Critical)
            .value("Question", QMessageBox::Icon::Question)
            ;

    py::class_<QPushButton>(m, "QPushButton")
            ;

    // QSettings class
    py::class_<QSettings>(m, "QSettings")
            .def(py::init<>())
            .def("setValue", [](QSettings& settings, const py::str& key, const py::str& value){ settings.setValue(key.cast<QString>(), value.cast<QString>());})
            .def("value",
                [](QSettings& settings, const py::str& key, const py::str& defaultValue){ return settings.value(key.cast<QString>(), defaultValue.cast<QString>()).toString().toStdString();},
                py::arg("key"),
                py::arg("defaultValue") = std::string()
            )

            ;

    // QFileDialog

    m.def("getOpenFileName",
          [](const QString& caption, const QString& dir, const QString& filter)
        {
            return QFileDialog::getOpenFileName(MainWindow::getInstance(), caption, dir, filter);
        }
    );
    m.def("getSaveFileName",
          [](const QString& caption, const QString& dir, const QString& filter)
        {
            return QFileDialog::getSaveFileName(MainWindow::getInstance(), caption, dir, filter);
        }
    );
    m.def("getExistingDirectory",
          [](const QString& caption, const QString& dir)
        {
            return QFileDialog::getExistingDirectory(MainWindow::getInstance(), caption, dir);
        }
    );

    // MoveCommand::MoveDirection enum
    py::enum_<MoveCommand::MoveDirection>(m, "MoveDirection")
            .value("up", MoveCommand::MoveDirection::up)
            .value("down", MoveCommand::MoveDirection::down)
            ;
    // MODULE GLOBAL
    m.def("ignoreNextFileModifiedSignalFromFile",
          [](const QString& file) {
       MainWindow::getInstance()->getFileWatcher()->ignoreNextSignalFromFile(file);
    });
    m.def("loadMap",
         [](const py::str& filename, MainWindow* main_window) {
            return MemoryNode::fromFile(filename.cast<std::string>(), main_window->getSchema(), main_window->getValidator());
         });
    m.def("getWritableTempPath", []{
        return QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString();
    });
    // Messages
    m.def("warn",       [](py::str& string) {qWarning("%s", string.cast<std::string>().c_str());});
    m.def("fatal",      [](py::str& string) {qFatal("%s", string.cast<std::string>().c_str());});
    m.def("debug",      [](py::str& string) {qDebug("%s", string.cast<std::string>().c_str());});
    m.def("info",       [](py::str& string) {qInfo("%s", string.cast<std::string>().c_str());});
    m.def("critical",   [](py::str& string) {qCritical("%s", string.cast<std::string>().c_str());});
    // exceptions
    py::register_exception<YAML::Exception>(m, "YAMLException");
    py::register_exception<YAML::BadFile>(m, "YAMLFileNotFound");


    m.def("updateGlobals", [](py::dict globals){
        MainWindow::getInstance()->interpreter_widget->updateGlobals(globals);
    });
    m.def("updatePrompt", [](const QString& prompt) {
        MainWindow::getInstance()->interpreter_widget->updatePrompt(prompt);
    });
//    m.def("userInput", [](const QString& prompt){
////        return QInputDialog::getMultiLineText(MainWindow::getInstance(), prompt, prompt).toStdString();
//        PythonInterpreterDialog* dialog = MainWindow::getInstance()->interpreter_dialog;
//        dialog->setPrompt(prompt);
//        int result = dialog->exec();
//        if (result == QDialog::Accepted)
//        {
//            const QString& text_results = dialog->textResult();
//            qDebug() << text_results;
//            return text_results.toStdString();
//        }
//        else
//        {
//            throw py::eof_error();
//        }
////        bool ok;
////        const QString& result = QInputDialog::getText(MainWindow::getInstance(), "Python prompt", prompt, QLineEdit::Normal, QString(), &ok);
////        if(ok)
////        {
////            qDebug() << result;
////            return result.toStdString();
////        }
////        else
////            throw py::eof_error();
//    }, py::arg("prompt") = "");
    // commands
    m.def("changeAttributeCommand",
          [](const QModelIndex& index, const QModelIndex& nodes_model_parent, const QString& val) {
            MainWindow::getInstance()->getUndoStack()->push(new ChangeAttributeCommand(index, nodes_model_parent, val));
        });
    m.def("addChildCommand",
          [](const int& position, const QString& type, const QModelIndex& nodes_model_parent) {
            MainWindow::getInstance()->getUndoStack()->push(new AddChildCommand(position, type, nodes_model_parent));
        });
    m.def("addChildrenCommand",
          [](const std::vector<std::string>& types, const QModelIndex& nodes_model_parent) {
            MainWindow::getInstance()->getUndoStack()->push(new AddChildrenCommand(types, nodes_model_parent));
        });
    m.def("moveCommand",
          [](MoveCommand::MoveDirection direction, const QModelIndex& index, int count) {
            MainWindow::getInstance()->getUndoStack()->push(new MoveCommand(direction, index, count));
        });
    m.def("removeNodeCommand",
          [](const QModelIndex& index) {
            MainWindow::getInstance()->getUndoStack()->push(new RemoveNodeCommand(index));
        });
    m.def("duplicateNodeCommand",
          [](const QModelIndex& index, int targetPosition, const QModelIndex& target_index) {
            MainWindow::getInstance()->getUndoStack()->push(new DuplicateNodeCommand(index, targetPosition, target_index));
        });
    m.def("addAttributeContainerCommand",
          [](const int& position, const QString name, const QModelIndex& node_index, const QModelIndex& container_index, AttributesModel* model) {
            MainWindow::getInstance()->getUndoStack()->push(new AddAttributeContainerCommand(position, name, node_index, container_index, model));
        });
    m.def("addAttributeCommand",
          [](const QString name, const QModelIndex& node_index, const QModelIndex& container_index, AttributesModel* model, const QString value) {
            MainWindow::getInstance()->getUndoStack()->push(new AddAttributeCommand(name, node_index, container_index, model, value));
        }, py::arg("name"), py::arg("node_index"), py::arg("container_index"), py::arg("model"), py::arg("value") = std::string());
    m.def("removeAttributeCommand",
          [](const QModelIndex& index, const QModelIndex& node_index) {
            MainWindow::getInstance()->getUndoStack()->push(new RemoveAttributeCommand(index, node_index));
        });
    m.def("removeAttributeContainerCommand",
          [](const QModelIndex& index, const QModelIndex& node_index) {
            MainWindow::getInstance()->getUndoStack()->push(new RemoveAttributeContainerCommand(index, node_index));
        });
    m.def("dragNodeCommand",
          [](const QModelIndex &sourceParent, int sourcePosition, int sourceCount, const QModelIndex &destinationParent, int destPosition) {
            MainWindow::getInstance()->getUndoStack()->push(new DragNodeCommand(sourceParent, sourcePosition, sourceCount, destinationParent, destPosition));
        });
    m.def("dummyCommand", [](const QModelIndex& index, const QString& text){MainWindow::getInstance()->getUndoStack()->push(new DummyCommand(index, text));});
}
